Commit graph

890 commits

Author SHA1 Message Date
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
e9101b0d44 chore: archive v3.0 milestone files 2026-07-17 06:56:49 -04:00
8b04be160f docs: fix stale requirement checkboxes (CLASSIFY-01..06, REVIEW-03), record milestone-close deferred items 2026-07-17 06:48:16 -04:00
8610e7ab9b docs(22): add first-ever phase verification (human_needed — code passes, 5 UAT checks outstanding) 2026-07-17 06:47:18 -04:00
369ae31dba docs(23): mark phase complete — milestone v3.0 finished 2026-07-16 23:34:15 -04:00
2e55e5e91d docs(23): re-verify phase — AUTOGATE-03 gap closed, 18/18 passed 2026-07-16 23:33:07 -04:00
6e8c78b8d2 fix(23-06): capture actionId in auto-post audit payload, guard manual re-approval of acknowledge_user
23-06-REVIEW.md found two real defects in the just-merged idempotency fix:
- CR-01: autoPostAcknowledgment's audit payload omitted actionId, which the
  campaign-detail API requires to derive completedAt — every auto-posted
  acknowledge_user row rendered a null completion date in the Action Area UI.
- CR-02: the manual approve/remediate path had no server-side guard against
  re-approving acknowledge_user for a campaign that already got auto-posted —
  only a client-side UI check prevented the exact duplicate-note bug 23-06
  was chartered to close, reachable via a direct API call.

Fixes both: capture RETURNING id from the insert and include it in the audit
payload; add an existence check in approveRemediationActions that rejects
acknowledge_user when already posted for the campaign.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 23:29:11 -04:00
9dfc9162df docs(23-06): add code review report 2026-07-16 23:29:03 -04:00
f44cdd2910 docs(phase-23): update tracking after wave 3 2026-07-16 23:21:24 -04:00
273ac9cb89 chore: merge executor worktree (worktree-agent-a172feca473a48833) 2026-07-16 23:20:29 -04:00
605e994937 docs(23-06): append self-check results to SUMMARY
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 23:20:04 -04:00
3bd116ad5b docs(23-06): complete idempotent auto-post acknowledgment plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 23:19:49 -04:00
13bf851ab5 fix(23-06): wire auto_report webhook branch to autoPostAcknowledgment
Replace the unguarded generateAndPostAcknowledgment(campaignId) direct
call in runGatedPhishingStages' auto_report branch with the idempotent
autoPostAcknowledgment(campaignId, 'system:auto_report'). Closes CR-01
(23-REVIEW.md) / Truth #18 (23-VERIFICATION.md): a repeat ticket-create
webhook joining an already-acknowledged USER_AWARENESS campaign now
finds the persisted remediation_actions row and skips the re-post
instead of re-sending the customer-visible thank-you note on every
additional report.
2026-07-16 23:18:47 -04:00
c79af9b448 feat(23-06): add idempotent autoPostAcknowledgment to remediation-service
- New autoPostAcknowledgment(campaignId, actor) mirrors the VERIFIED
  remediateApprovedActions shape: campaign row lock, idempotency check
  against an existing acknowledge_user remediation_actions row, insert +
  audit inside one transaction, note post after commit (non-fatal on
  failure)
- Closes CR-01/WR-01: gives the auto_report webhook path a persisted
  record so a repeat ticket-create webhook joining an already-acked
  campaign does not re-insert/re-audit/re-post
- Adds Test A/B/C in remediation-service.test.ts covering first-pass
  insert, idempotent skip, and non-fatal note-post failure
2026-07-16 23:17:28 -04:00
a918b72d04 docs(23): gap-closure plan for AUTOGATE-03 idempotency defect (CR-01) 2026-07-16 22:54:19 -04:00
cc87607a58 docs(23): add phase verification report (gaps found) 2026-07-16 21:17:14 -04:00
c243cbc41f docs(23): add code review report 2026-07-16 21:11:48 -04:00
dadac5180d docs(phase-23): update tracking after wave 2 2026-07-16 21:04:14 -04:00
05a31d6a65 chore: merge executor worktree (worktree-agent-afdf88c6384b105cc) 2026-07-16 21:02:50 -04:00
032160cfe9 chore: merge executor worktree (worktree-agent-ab40944a8ae803901) 2026-07-16 21:02:50 -04:00
7e33a8568b docs(23-04): complete admin phishing-automation page plan
- Adds 23-04-SUMMARY.md documenting Task 1 (page) and Task 2 (index tile)
- Records that Task 1's human-verify checkpoint was approved via a
  temporary production container rebuild rather than local npm run dev
2026-07-16 21:02:00 -04:00
160fe88253 feat(23-04): add admin index tile for Phishing Automation
- Adds a Phishing Automation tile to the Tools & Data section of /admin
- Links to /admin/phishing-automation, imports Bot icon from lucide-react
2026-07-16 21:01:08 -04:00
7869e0bcfd docs(23-05): complete gated automation pipeline plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
2026-07-16 19:47:21 -04:00
5dabaad722 feat(23-04): build /admin/phishing-automation page with per-stage toggles
- Company table with search + type filter (cloned from client-scope pattern)
- Three independent Switch toggles per row: auto-parse, auto-classify, auto-report
- toggle() PATCHes /api/admin/phishing-automation/{companyId} with all three current flags
- Helper caption clarifies stage dependency (informational, not enforced)
2026-07-16 19:46:16 -04:00
e1193bf476 feat(23-05): wire gated parse->classify->acknowledge chain into webhook
- triggerPhishingDetection now captures groupReportIntoCampaign's result and,
  when a campaignId exists, calls new runGatedPhishingStages
- runGatedPhishingStages reads the per-company automation gate and
  conditionally runs parseAndStoreMessage, classifyCampaign, and (only for
  USER_AWARENESS verdicts) generateAndPostAcknowledgment
- each stage isolated in its own try/catch (T-23-09); detection + grouping
  remain unconditional (D-07); auto_report never posts any other action
  (D-04, T-23-08)
2026-07-16 19:46:15 -04:00