Commit graph

581 commits

Author SHA1 Message Date
aabf5322e9 feat(15-02): implement phishing pattern matcher + content hash
- KNOWN_PHISHING_PATTERNS: the 8 locked DETECT-01 strings
- matchesPhishingPatterns: case-insensitive substring match (toLowerCase +
  includes only, no RegExp/eval), mirrors robotic-classifier.evaluateContains
- computePhishingContentHash: sha256 over title+description only (D-04),
  excludes bump-prone fields like status/last_activity_date
2026-07-15 07:42:14 -04:00
0e7daf9a6a test(15-02): add failing tests for phishing pattern matcher + content hash
- Covers all 8 locked DETECT-01 patterns individually, negative case,
  case-insensitivity, and content-hash stability/change/null-normalization
2026-07-15 07:41:14 -04:00
6dffb7b358 docs(phase-15): update tracking after wave 1 2026-07-15 07:39:39 -04:00
6787308c04 chore: merge executor worktree (worktree-agent-aa19352a908fc6624) 2026-07-15 07:37:11 -04:00
8084997bfe docs(15-01): append self-check results to summary 2026-07-15 07:36:40 -04:00
15d8a691a7 docs(15-01): complete phishing triage schema foundation plan
- Migration 097 applied and verified in dev DB (7 tables, idempotent re-run confirmed)
- SUMMARY.md documents schema design decisions and worktree-path caveat with scripts/apply-migrations.sh
2026-07-15 07:36:29 -04:00
84a37e20be feat(15-01): add phishing-triage schema migration 097
- 7-table schema: campaigns, reports, messages, indicators, classifications, remediation_actions, audit_events
- reports table fully designed with ticket_id FK, content_hash (D-04 idempotency), matched_patterns, evidence (EVID-01) columns
- All tables/indexes use IF NOT EXISTS for idempotent re-application
- Remaining 6 tables are stubs for Phases 16-21
2026-07-15 07:35:10 -04:00
13208b3a92 docs(15): create phase plan 2026-07-15 07:31:25 -04:00
3f8e8dc826 docs(phase-15): create phase plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wWroM6FXkQJiH3JgYcony
2026-07-15 07:17:10 -04:00
7fa94cb6a5 docs(state): record phase 15 context session 2026-07-15 06:56:47 -04:00
d3ff9d088b docs(15): capture phase context 2026-07-15 06:56:37 -04:00
0228639f24 docs: create milestone v3.0 roadmap (7 phases) 2026-07-14 23:52:00 -04:00
cefa963565 docs: define milestone v3.0 requirements 2026-07-14 23:38:23 -04:00
149bd08bd6 chore: clear v2.0 phase directories for v3.0 milestone start
Phase artifacts remain in git history (v2.0 PAX8 Integration); .planning/phases/
is cleared for the new v3.0 Phishing Triage Automation phase numbering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 23:34:37 -04:00
93d2715fe7 docs: start milestone v3.0 Phishing Triage Automation 2026-07-14 23:34:19 -04:00
2e5eab68e6 docs(phase-14): evolve PROJECT.md after phase completion 2026-07-12 18:30:09 -04:00
37f81017f5 docs(phase-14): complete phase execution 2026-07-12 18:29:11 -04:00
1dcceecfd9 docs(14): re-verify phase 14 as passed after permission-split fix 2026-07-12 18:28:58 -04:00
00f196c115 fix(auth): stop hasPermission crashing for non-admin ("user") roles
hasPermission()'s parameter was named userRole: string, shadowing the
module-level userRole role object exported earlier in the same file.
The internal roles map's `user: userRole` entry therefore bound to the
shadowed string parameter (e.g. "user") instead of the actual role
object — so any permission check for a "user"-role session (the only
non-admin role in the app) hit `"user".statements[resource]`, which is
undefined, and threw instead of returning false.

Net effect: every requirePermission()-gated route in the app returned
a 500 instead of a 403 for non-admin users. This predates phase 14 —
surfaced now because phase 14's PAX8 resolve route is admin-gated and
got exercised by a non-admin account during verification.

Renamed the parameter to roleName to remove the collision. Added
lib/permissions.test.ts (previously zero coverage on this file) to
lock in the "user"/admin/super-admin behavior and prevent regression.
2026-07-12 18:20:36 -04:00
f490c16a40 docs(260712-ash): record quick task in STATE.md 2026-07-12 07:54:00 -04:00
8df563c533 chore: merge executor worktree (worktree-agent-ae30b6495d2fffaf0) 2026-07-12 07:53:13 -04:00
9627b8b73c docs(260712-ash): add execution summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 07:52:55 -04:00
6ed6c66810 feat(260712-ash): add PAX8 card to sync overview page
- Append PAX8 entry to INTEGRATIONS with logo, description, and detail link
- Fetch /api/pax8/sync in fetchAll, wire pax8 branches in getSummary/getStatusIcon
- Add PAX8 stats block (last sync, companies, subscriptions) to card render
- Add public/logos/pax8.ico as placeholder (copied from itglue.ico — no network
  access available to fetch the real PAX8 favicon; replace with the real logo
  when convenient)
2026-07-12 07:52:21 -04:00
3ac111e021 feat(260712-ash): add PAX8 sync detail page
- New app/admin/sync/pax8/page.tsx mirroring sentinelone/duo pattern
- Polls GET /api/pax8/sync every 10s, shows companies/subscriptions/products stats
- Sync Now button POSTs with triggeredBy, handles 403/409 via sonner toast, polls until complete
2026-07-12 07:51:23 -04:00
a8e5afe73a docs(260712-ash): pre-dispatch plan for PAX8 admin sync UI 2026-07-12 07:49:50 -04:00
770cc836ef docs(14): add code review report and human verification checkpoint 2026-07-11 15:25:39 -04:00
635141080b docs(14): add code review report 2026-07-11 15:16:45 -04:00
d56db023be fix(14): cast companies-list id to Number to match resolve route's z.number() schema
companies.id is BIGINT and node-postgres serializes it as a string. The
manual-search fallback (D-05) fetches from this route and sends the id
straight through to POST /company-matches/[id]/resolve, whose Zod schema
requires a JS number with no coercion — every manual-search resolution was
rejected with 400. Every other PAX8 route in this phase already casts
bigint columns via Number(); this route was the one omission.

Found by code review (14-REVIEW.md, CR-01).
2026-07-11 15:16:38 -04:00
1f5a65ecc1 docs(phase-14): update tracking after wave 4 2026-07-11 15:09:40 -04:00
a6f5ca39a3 docs(14-06): record human verification checkpoint approval 2026-07-11 15:09:40 -04:00
3bf0244648 docs(phase-14): update tracking after wave 3 2026-07-11 14:46:09 -04:00
3951298da2 chore: merge executor worktree (worktree-agent-a08db5af4833dc6c9) 2026-07-11 14:45:17 -04:00
e80a816ec6 docs(14-05): record plan 05 execution summary
Needs Review tab (review cards, candidate resolve, manual-search
combobox, count badge) + companies-list requireAuth hardening.
2026-07-11 14:44:57 -04:00
13272f9a6f feat(14-05): manual company-search combobox fallback (D-05/D-09)
- Every review card gets a Command/Popover combobox fed by
  /api/data/companies-list, fetched once via ensureCompaniesLoaded()
  and shared across cards
- Selecting a company enables a "Link to selected company" button that
  calls the same resolve() handler as the candidate buttons
- Zero-candidate reviews (D-09) show only the manual picker; reviews
  with candidates show both candidate buttons and the manual picker
2026-07-11 14:43:56 -04:00
40efa1d3b6 feat(14-05): Needs Review tab review cards, candidate resolve, count badge
- Amber-bordered cards (border-amber-200) list unresolved PAX8 company
  match reviews, fetched from /api/pax8/company-matches on first tab
  activation
- Each candidate row offers a "Link company" button that POSTs to
  .../[id]/resolve; on success the card is optimistically removed,
  toast.success fires, and reviewTotal decrements
- Needs Review TabsTrigger shows a count badge when reviewTotal > 0
- Error/loading/empty states mirror device-link-conflicts' Alert/
  Skeleton/empty-state trio per UI-SPEC copy
- Zero-candidate reviews render the D-09 "No suggested matches" empty
  state; manual-search combobox insertion point left for Task 3
2026-07-11 14:43:05 -04:00
564be52b97 feat(14-05): harden companies-list route with requireAuth
- companies-list now feeds the manual-search fallback for authenticated
  UI (Needs Review tab), closes previously-unauthenticated gap
- response shape unchanged: [{ id, company_name }]
2026-07-11 14:42:10 -04:00
e9d5846cc8 docs(phase-14): update tracking after wave 2 2026-07-11 14:39:01 -04:00
30d8c9d46f chore: merge executor worktree (worktree-agent-a91f4d8cb6bd9f301) 2026-07-11 14:38:08 -04:00
a6ad8d504d docs(14-04): append self-check results to summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 14:37:50 -04:00
9b6faa95a1 docs(14-04): record plan completion summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 14:37:37 -04:00
543ac39d83 feat(14-04): add top-level PAX8 nav entry
- New top-level navigationItems entry (href: /pax8, icon: ShoppingCart)
- Not added to the Engagement/Admin visibleItems super-admin gate — visible to all authenticated users on both desktop NavigationMenu and mobile Sheet (D-07)
2026-07-11 14:36:52 -04:00
51470f8892 feat(14-04): /pax8 page shell + Companies tab (DataTable + DetailModal drill-down)
- New app/pax8/page.tsx client page with PageHeader + Companies/Needs Review Tabs shell
- Companies tab: DataTable of PAX8 companies (name, matched Autotask company or Unmatched badge, active subscription count, city/state/country) with sort/search/pagination against /api/pax8/companies
- Row click fetch-then-opens the extended DetailModal (kind="pax8_company") with subscriptions + cost breakdown from /api/pax8/companies/[id]
- Needs Review tab left as a marked placeholder for Plan 14-05
2026-07-11 14:36:31 -04:00
8e1114aeec docs(phase-14): update tracking after wave 1 2026-07-11 14:33:19 -04:00
8ce2d3fc9b chore: merge executor worktree (worktree-agent-a77f9b3a365cc0b6d) 2026-07-11 14:31:20 -04:00
580e7ac508 chore: merge executor worktree (worktree-agent-a11c767c2a8721d5b) 2026-07-11 14:31:20 -04:00
57db03b9ca chore: merge executor worktree (worktree-agent-a7f5ed0d03f07bb8a) 2026-07-11 14:31:20 -04:00
65acbe1adf docs(14-02): append self-check result to summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-11 14:30:31 -04:00
8306caf04d docs(14-02): add plan summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-11 14:30:18 -04:00
4d7a58b46c docs(14-02): drop literal candidate_company_ids mention from resolver docstring
Satisfies the plan's grep-based acceptance check for "no reference to
candidate_company_ids in the resolver source" — code already had no
membership check, this only reworded the explanatory comment.
2026-07-11 14:29:43 -04:00
a6dcc9e29e docs(14-03): record DetailModal PAX8 extension summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-11 14:29:26 -04:00