Commit graph

964 commits

Author SHA1 Message Date
52affe348c docs(24): cite D-02/D-05/D-08/D-09 in plan must_haves 2026-08-05 19:17:35 -04:00
15e52280b5 docs(24): create phase plan — 7 plans in 4 waves for AWS Route 53 DNS sync 2026-08-05 19:09:15 -04:00
6ad27fcbb6 docs(phase-24): add validation strategy 2026-08-05 18:46:43 -04:00
e289f2d24f docs(state): record phase 24 context session 2026-08-05 18:31:11 -04:00
0805e387e3 docs(24): capture phase context 2026-08-05 18:31:06 -04:00
5f308f836f fix(phishing-recipient-seubert): scope campaign grouping Tier 3 to company, not reporting contact
Tier 3 is the only tier automatic (webhook-triggered) grouping ever
reaches, since grouping runs before message parsing. It was scoped to
reports.requester_contact_id, so the same campaign reported by
different employees at the same company never consolidated into one
campaign — each report's evidence/blast-radius view silently
under-reported the campaign's true recipients.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 12:34:33 -04:00
e9478101a3 docs(quick-260721-n49): fix classifier to resolve per-company Mimecast tenant instead of the global tenant 2026-07-21 16:48:11 -04:00
1cd0d4db48 chore: merge quick task worktree (worktree-agent-a498794d9a221b8b7) 2026-07-21 16:47:36 -04:00
9f12cd610a test(260721-n49): cover tenant-scoped vs global-fallback getBlastRadius branches
- Add mimecast_tenants routing branch to stageQueries and company_id to
  report fixtures
- New test: enabled mimecast_tenants row -> getBlastRadius called with
  { client, cacheScope: companyId } and the tenant SQL is issued
- New parameterized test: null companyId and companyId-with-no-enabled-row
  -> getBlastRadius called with no second argument (global fallback)
2026-07-21 16:45:44 -04:00
9f75f2160c fix(260721-n49): resolve per-company Mimecast tenant in gatherCampaignEvidence
- Thread company_id through reports query and CampaignReportSummary
- Mirror the route's Bug 2 (D-05) tenant-resolution block: query
  mimecast_tenants for an enabled row, build a tenant-scoped client via
  getMimecastClientForTenant, and pass { client, cacheScope } to
  getBlastRadius() when one exists
- Preserve global env fallback unchanged when no companyId or no
  enabled tenant row is present
2026-07-21 16:44:44 -04:00
f58856e103 docs(260721-n49): pre-dispatch plan for fix classifier Mimecast tenant resolution 2026-07-21 16:41:14 -04:00
3e9c7633d1 docs(quick-260721-mmf): fix Mimecast blast-radius query so it reflects the true blast radius across all recipients, not just the single reporter 2026-07-21 16:26:57 -04:00
534eda3c40 test(260721-mmf): assert tenant-wide fan-out and multi-recipient blast radius
- Updated the getHeldMessages window test to assert no `recipient` key and
  added an explicit no-`to` assertion for searchDeliveredMessages
- Added a new test driving searchDeliveredMessages with 3 distinct `to`
  addresses, asserting all appear in perRecipient and count toward
  matched/delivered
2026-07-21 16:25:39 -04:00
58202e0dee fix(260721-mmf): broaden Mimecast blast-radius fan-out to whole tenant
- searchDeliveredMessages now called with from+subject+start+end only
  (no `to`) so it returns every delivered/rejected message matching the
  campaign across all recipients, not just the reporter's mailbox
- getHeldMessages now called with start+end only (no `recipient`) —
  domainsMatch() post-filter is the sole scoping mechanism for held rows
- Updated inline comments to document the tenant-wide fan-out and the
  per-recipient merge behavior it now produces
2026-07-21 16:25:08 -04:00
f50b8a3a9b docs(260721-mmf): pre-dispatch plan for fix Mimecast blast-radius query scope 2026-07-21 16:19:31 -04:00
6eafaae40a docs(quick-260721-fy8): fix mimecast and qbo sync scheduler dispatch and reschedule mimecast cron
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 11:39:52 -04:00
db7db98374 chore: merge quick task worktree (worktree-agent-aed3ab3e5ca2a6fb2) 2026-07-21 11:38:55 -04:00
f447ac33c0 fix(quick-260721-fy8): reschedule mimecast-sync off the 2am cron collision
- Add migration 101: guarded UPDATE moving mimecast-sync from 0 2 * * * to
  45 4 * * * (minute 45 is unused by any other schedule row)
- Guarded on the stale cron value so it's a no-op if already moved and won't
  clobber an admin's manual schedule change
- Applied the same UPDATE directly to the live pulse-postgres container
  (migrations only auto-apply on first volume boot, per CLAUDE.md)
- Eliminates the 3-way 2 AM collision with qbo-sync-2am and veeam-full
2026-07-21 11:36:56 -04:00
9311f1044b fix(quick-260721-fy8): add mimecast-sync and qbo dispatch branches to scheduler
- Extend ScheduleConfig.sync_type union with 'mimecast-sync'
- Add mimecast-sync branch calling runMimecastIncrementalSync() behind
  isMimecastConfigured(), mirroring the engagement/zoom configured-gate pattern
- Add qbo branch calling getQboSyncService().incrementalSync('scheduled')
  behind an integration_settings disabled check, mirroring the pax8-daily
  disable-check pattern
- Both branches previously fell through to the generic Autotask fullSync()
  catch-all, which also contended for the SyncService singleton mutex
2026-07-21 11:36:16 -04:00
d41d6e7c0b docs(260721-fy8): pre-dispatch plan for fix mimecast and qbo sync scheduler dispatch and reschedule mimecast cron 2026-07-21 11:33:33 -04:00
a0d01cfe8b docs(v3.0): add LLM-context companion doc for phishing triage feature 2026-07-18 11:22:09 -04:00
f0c09927f4 docs(v3.0): generate milestone summary for onboarding 2026-07-18 11:20:31 -04:00
a65b29055c docs(quick-260718-9qg): complete QBO integration handoff quick task
Records the plan, execution summary, and STATE.md quick-task log entry
for the QBO_INTEGRATION_HANDOFF.md doc committed in ea8a36b.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-18 07:08:29 -04:00
ea8a36b394 docs(quick-260718-9qg): add self-contained QBO integration handoff document
Documents Pulse's QuickBooks Online integration (OAuth2 authorization-code
flow, token storage/refresh, sandbox vs production API base URLs, scopes,
minor version, and gotchas learned from the AR reconciliation/soft-delete
work) so a new app's team can build their own QBO connection without
access to the Pulse codebase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-18 07:07:26 -04:00
672f17b7f9 chore: check in pending work — queue preferences, QBO AR diagnostics, mobile engagement fixes, ops scripts
Bundles several in-progress efforts that were sitting uncommitted:
- User queue-preferences (migration 087, API route, popover component)
- QBO invoice soft-delete (migration 088) and AR diagnostics route
- Dashboard/mobile engagement route and page adjustments
- Docker Compose log-rotation config
- One-off ticket/RMM investigation scripts (scripts/)
- Planning docs: phase verification/pattern notes, mobile shell design spec
- .gitignore: exclude local scratch financial/inventory data and Claude Code
  worktree/local-settings runtime state (never meant for version control)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-18 06:34:57 -04:00
b638189cb0 docs: document get-hold-message-list endpoint and its held-queue filtering gotcha 2026-07-18 06:10:59 -04:00
5b09dbff96 fix: tighten Headers tab grid layout and wrap long values instead of overflowing the card 2026-07-18 06:04:37 -04:00
a30d72e9ba docs(quick-260718-7v8): Fix Mimecast blast-radius held-message false-positive fix 2026-07-18 05:48:23 -04:00
b7d6be47c6 chore: merge quick task worktree (worktree-agent-a45f7294477ed0d95) 2026-07-18 05:47:50 -04:00
67ee680105 test(quick-260718-7v8): cover held-message date-scoping and sender-relevance guard
- blast-radius: getHeldMessages called with same start/end window as
  searchDeliveredMessages
- blast-radius: unrelated-sender held row excluded from held/matched and
  perRecipient; matching-sender held row still counts and overrides
- client: getHeldMessages threads start/end into POST body data[0] when
  provided, omits them when not
2026-07-18 05:46:22 -04:00
94f7dad29c fix(quick-260718-7v8): date-scope held-message lookup + sender-relevance guard
- getHeldMessages() accepts optional start/end, threaded into data[0] as
  siblings of admin/searchBy (backward compatible when omitted; 403
  fallback body inherits them automatically via the existing spread)
- getBlastRadius() passes the same startStr/endStr window already
  computed for searchDeliveredMessages into getHeldMessages()
- Added domainsMatch() sender-relevance guard: held rows whose sender
  domain doesn't match input.sender (exact-or-proper-subdomain) are
  filtered out before counting/merging, so unrelated same-window holds
  never inflate held/matched or override a delivered recipient
2026-07-18 05:44:56 -04:00
36f4d418d0 docs(260718-7v8): pre-dispatch plan for Mimecast blast-radius held-message fix 2026-07-18 05:42:27 -04:00
4a77b0544c docs(quick-260717-v6c): Add Mark as accidental report action to phishing Action Area 2026-07-17 22:37:25 -04:00
565a0c1ee2 chore: merge quick task worktree (worktree-agent-a3ab410880ddc437a) 2026-07-17 22:36:29 -04:00
0d6cd25008 test(260717-v6c): mirror test coverage for accidental-report service functions
- remediation-service.test.ts: D-04 guard rejection, successful status flip
  + audit event, note-post-failure-still-commits path for
  markCampaignAccidentalReport
- triage-note-service.test.ts: noteType 18/publish 1 posting, per-ticket
  isolation, zero-reports path for generateAndPostAccidentalReportNote
2026-07-17 22:35:04 -04:00
74e43e23c4 feat(260717-v6c): add Mark as accidental report button, dialog, and timeline case
- ActionAreaCard: new GatedButton + confirm AlertDialog (optional reason),
  resolved/tooltip logic now covers accidental_report status, toast
  distinguishes full success from note-post failure
- TimelineCard: campaign_marked_accidental_report entry uses the
  blue/CheckCircle2 tint (distinct from slate/XCircle false-positive)
2026-07-17 22:34:09 -04:00
97804f2e5b feat(260717-v6c): add POST /api/phishing/campaigns/[id]/mark-accidental-report route
Mirrors mark-false-positive route exactly (requirePermission phishing/approve,
UUID validation, optional reason body, campaign-existence check, 409/400/500
error mapping) but calls markCampaignAccidentalReport and returns the richer
result including notePosted/noteError.
2026-07-17 22:33:09 -04:00
aea4fd2f0c feat(260717-v6c): add markCampaignAccidentalReport + generateAndPostAccidentalReportNote
- generateAndPostAccidentalReportNote mirrors generateAndPostAcknowledgment:
  fixed customer-visible template (noteType 18/publish 1), per-ticket
  try/catch isolation, zero evidence interpolation (T-23-01)
- markCampaignAccidentalReport mirrors markCampaignFalsePositive's D-04
  guard/transaction shape, then posts the note post-commit outside the
  FOR UPDATE lock; note-post failure never propagates
2026-07-17 22:32:45 -04:00
2b48dc0c10 docs(260717-v6c): pre-dispatch plan for Mark as accidental report action 2026-07-17 22:30:37 -04:00
ec6c5d05e6 fix: convert 0-1 scale confidence to percent in timeline-card (same bug as classification-card, missed in 260716-pgr) 2026-07-17 22:24:02 -04:00
952b63c8a4 fix: show pre-forward auth verdict (authResultsOriginal) instead of misleading post-forward SPF/DKIM/DMARC in evidence card 2026-07-17 21:00:25 -04:00
cff57a414e fix: update USER_AWARENESS acknowledgment note copy 2026-07-17 10:51:12 -04:00
90a1b684c9 docs: fix commit hash reference in quick task 260717-a19 STATE.md row 2026-07-17 07:27:14 -04:00
266fc19153 docs(quick-260717-a19): phishing allowlist/timing/idempotency fixes + Seubert reclassification 2026-07-17 07:27:02 -04:00
c2a64fab9a chore: merge quick task worktree (worktree-agent-ac1a694d510841c38) 2026-07-17 07:25:24 -04:00
78bb30aabc chore(quick-260717-a19): add one-off reclassify script for 6 Seubert campaigns
- Looks up campaign_id live per ticket (699419/699421/699422/699433/
  699435/699456), no hardcoded UUIDs
- Reads before verdict, calls classifyCampaign (D-02 append-only), prints
  before -> after verdict + confidence per ticket
- Dedupes shared campaign_ids so a shared campaign isn't classified twice
- Depends on Task 1's allowlist fix already being committed
2026-07-17 07:21:43 -04:00
cf04f07c58 feat(quick-260717-a19): add idempotency guard + retry-parse on ticket.update
- parseAndStoreMessage (Defect 3): short-circuit with
  { stored: false, reason: 'already-parsed' } when a messages row already
  exists for the report, before any Autotask attachment fetch
- webhook-service (Defect 2): new retryPhishingParseOnUpdate wired into
  ticket.update fire-and-forget path; retries the missing-EML parse for a
  flagged, unparsed, auto_parse-gated report — no new cron/polling, reuses
  existing update traffic, safe to fire repeatedly thanks to the new
  idempotency guard
- Adjust eml-service test mock default so the new leading existence-check
  query doesn't short-circuit existing happy-path tests; add new test for
  the already-parsed short-circuit
2026-07-17 07:20:48 -04:00
204276c88a feat(quick-260717-a19): add 3 confirmed KnowBe4 domains to sim allowlist
- Extend knowbe4 vendor entry with customer-portal.info,
  cloud-service-care.com, bankonlinesupport.com (confirmed via shared
  URL fingerprint across Seubert tickets 699419/699421/699422/699433/
  699435/699456 on 2026-07-16/17)
- domainMatchesAllowlist and isKnownSimulationSender untouched
- Add tests for exact match, subdomain match, and suffix-spoof rejection
2026-07-17 07:19:18 -04:00
075c4bbd24 chore: remove REQUIREMENTS.md for v3.0 milestone
Archived to .planning/milestones/v3.0-REQUIREMENTS.md. Fresh REQUIREMENTS.md
gets created when the next milestone's requirements are defined.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-17 06:58:12 -04:00
e5d42bda88 docs: update retrospective for v3.0 2026-07-17 06:57:43 -04:00