Commit Graph
23 Commits
Author SHA1 Message Date
adminandClaude Sonnet 4.6 d18d29168a feat(auth): phase 3 — replace Supabase GoTrue with bcryptjs+jose
Custom auth stack: bcryptjs password hashing (cost 10, GoTrue-compatible),
jose JWT session cookies (edge-safe, 8hr TTL), new API routes for
login/logout/reset/change-password, middleware rewritten to JWT-only
verification with no DB access. All 38 protected pages and API routes
migrated from supabase.auth.getUser() to getSession(). Supabase .from()
queries retained for Phase 4. lib/db/index.ts refactored to lazy Proxy
singleton to avoid module-level throw during Next.js build.

tsc: clean, build: clean, tests: 4/4 passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 16:20:04 +08:00
adminandClaude Sonnet 4.6 509ed90fe7 fix(capa): field-level auth split, status gate, RLS tightening, audit old_value, hide form on pending_verification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:10:53 +08:00
admin 52171ac3ca feat(db): add owner_notes column to capa_actions 2026-07-23 11:53:41 +08:00
adminandClaude Sonnet 4.6 e30d583947 feat: split alcohol and urine test into separate fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 19:08:27 +08:00
adminandClaude Sonnet 4.6 690485f74e feat: delete + deactivate for sites, zones, and trucks; harden incident API error handling
- zones: add active column (migration 20260713000005)
- sites/zones API: PATCH (toggle active) + DELETE (blocked when incidents reference it)
- trucks API: PATCH + DELETE with same pattern
- admin page: select active for sites + zones
- site-zone-manager + truck-manager: deactivate toggle + delete button per row with busyId
- incidents API: reject reports on deactivated zone/site; wrap handler in top-level try-catch so unhandled errors return JSON (not HTML)
- report-form: parse JSON separately so HTTP status code surfaces instead of generic "Something went wrong"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-13 16:05:36 +08:00
adminandClaude Sonnet 4.6 ebeab27e5e feat(db): trucks table, transport enum value, incidents.truck_id FK
- ALTER TYPE incident_type ADD VALUE 'transport' (isolated migration)
- trucks table with truck_no (unique), carrier, active, RLS mirroring sites
- incidents.truck_id UUID FK + index

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-13 10:49:06 +08:00
adminandClaude Sonnet 4.6 c00879bf66 fix: recover orphaned DeepSeek key + remove retired settings slots
Migration copies ANTHROPIC_API_KEY value → DEEPSEEK_API_KEY (DeepSeek key
was saved before the rename), then deletes ANTHROPIC_API_KEY and
VOYAGE_API_KEY rows. ALLOWED_KEYS now only lists active provider keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-13 07:11:09 +08:00
adminandClaude Sonnet 4.6 d10c690c12 feat: switch embeddings from Voyage AI to Google Gemini text-embedding-004
- embedText: call Gemini REST API (768-dim) instead of Voyage (1024-dim)
- Migration: drop+recreate incidents.embedding as vector(768), update
  match_incidents function, swap VOYAGE_API_KEY → GOOGLE_AI_API_KEY in app_settings
- Settings UI: relabel to "Google AI API Key (Embeddings)"
- All call sites updated (incidents POST, similar GET)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-13 06:50:35 +08:00
adminandClaude Sonnet 4.6 b2891a433d feat: switch AI provider from Anthropic to DeepSeek
- lib/claude/client.ts: replace Anthropic SDK with openai package pointed at DeepSeek baseURL
- 4 AI routes: port tool definitions, tool_choice, and output parsing to OpenAI function-calling format
- Drop thinking:{type:'adaptive'} (no DeepSeek equivalent); model string → deepseek-chat
- settings/route.ts: add DEEPSEEK_API_KEY to ALLOWED_KEYS
- migration: seed DEEPSEEK_API_KEY placeholder row in app_settings
- tests: update 3 AI route tests to mock createDeepSeekClient + OpenAI response shape

Voyage AI embedding path untouched (DeepSeek has no embeddings endpoint).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-12 21:51:15 +08:00
admin dba79c0f17 fix: drop-if-exists before recreating notifications RLS policies 2026-07-12 17:34:44 +08:00
admin c80091c8d6 security: CAPA privilege escalation, CSV injection, AI rate-limit, prompt injection guard, open redirect, timing-safe cron secret, server-only admin client, notifications RLS 2026-07-12 17:33:58 +08:00
adminandClaude Fable 5 576557181a feat: Phase 5 & 6 — usability, compliance hardening, analytics
Phase 5 (usability + compliance):
- In-app notification bell/badge: migration 016 adds read state + per-user
  RLS + create_in_app_notification SECURITY DEFINER RPC; /api/notifications;
  wired into incident creation, CAPA assign/verify, escalation cron
- Incident closure: new POST /api/incidents/[id]/close (requires verification
  status + all CAPAs verified); migration 017 locks closed incidents at DB
  level (update/delete triggers) with append-only incident_addenda + UI panel
- Server-side pagination on HSE/supervisor incident inboxes (.range, 25/page)
- Investigation form: alcohol/urine test result + witness statement refs
  (existing schema columns, now editable)
- Type-specific intake fields: migration 018 adds incidents.type_details
  JSONB; whitelist validation; environmental/asset/security/fire field
  groups in report form; EN/MS/ZH labels; offline queue support
- JKKP 8 annual register CSV export (/api/reports/jkkp8) + dashboard button
  + January statutory deadline banner
- Admin page: user invite (service-role client), role/site/active management,
  site + zone CRUD with QR report links — replaces Phase 0 stub
- Evidence gallery thumbnails via Supabase render transform with fallback

Phase 6 (analytics):
- 12-month stacked trend chart (leading/lagging/other) + top root causes
  (lib/dashboard/trends.ts pure helpers)
- AI rising-risk zones: /api/dashboard/ai/risk-flags aggregates 90-day
  zone stats, claude-opus-4-8 forced tool_use, panel on HSE + management
  dashboards, suggestion audit-logged

Also fixes 9 pre-existing missing /ims basePath prefixes in client fetches
and download links.

132 tests passing, tsc clean, next build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
2026-07-12 10:25:08 +08:00
adminandClaude Sonnet 4.6 edb0bde4c5 fix: restore phase4 migration (recheck_round column + WhatsApp app_settings rows)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFDuBhMKvoWjrWT3ZnGmmr
2026-07-11 18:51:44 +08:00
admin 121433ddf8 Revert "feat: WhatsApp notification helper + phase 4 migration (recheck_round, WhatsApp settings)"
This reverts commit b6845f5bb5.
2026-07-11 18:47:08 +08:00
admin b6845f5bb5 feat: WhatsApp notification helper + phase 4 migration (recheck_round, WhatsApp settings) 2026-07-11 18:43:57 +08:00
adminandClaude Sonnet 4.6 b9ab94c9da feat: API key settings page — store ANTHROPIC/VOYAGE keys in DB with admin UI
- Migration: app_settings table with admin-only RLS (ANTHROPIC_API_KEY, VOYAGE_API_KEY)
- lib/settings.ts: getApiKey() reads DB first, falls back to env var
- lib/claude/client.ts: factory createAnthropicClient(apiKey) replaces singleton
- lib/claude/embed.ts: optional apiKey param, falls back to env
- 3 Claude AI routes + similar route: fetch key from settings before calling AI
- incidents/route.ts: fire-and-forget embed reads VOYAGE key from settings
- GET/POST /api/settings: admin-only masked key management endpoint
- /hse/settings page + ApiKeyForm client component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFDuBhMKvoWjrWT3ZnGmmr
2026-07-11 17:46:40 +08:00
adminandClaude Sonnet 4.6 c07bdb77ad fix: RLS guard in match_incidents + try/catch around AI/embed calls
- Add new migration 20260711000013_match_incidents_auth_guard.sql that
  replaces match_incidents with an inline auth guard: callers without
  hse/admin role receive PGRST301 Forbidden, closing the SECURITY
  DEFINER RLS bypass.
- Wrap anthropic.messages.create() in try/catch returning 503 in all
  four AI routes: quality-check, triage-suggest, rca-draft, similar.
- Wrap JSON.parse(inc.embedding) and embedText() in similar/route.ts
  in a shared try/catch returning 503 Embedding service unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFDuBhMKvoWjrWT3ZnGmmr
2026-07-11 16:48:20 +08:00
admin ab6299f963 feat: pgvector extension + incidents.embedding column + match_incidents RPC 2026-07-11 16:14:32 +08:00
admin a718a2ddbb fix: drop write_audit_log before recreate to handle existing function with different param names 2026-07-11 14:50:03 +08:00
admin dbbb663611 feat: phase2 migration — triage fields, RCA structured data, write_audit_log RPC 2026-07-11 08:35:51 +08:00
admin 8a9758c8a6 feat: Phase 1 foundation - incident components, storage, and migrations 2026-07-10 10:48:56 +08:00
admin 28957f1e0f fix: patch critical auth and RLS security findings from final review 2026-07-10 06:01:36 +08:00
adminandClaude Sonnet 4.6 05daf70177 feat: add database schema migrations and seed data
8 SQL migration files covering all 10 tables, RLS policies with
SECURITY DEFINER helpers, and seed data for SCW1 site + 3 zones.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWxyMibCuGGtSQSqfajDQ7
2026-07-09 21:44:24 +08:00