Files
iistwin/IMPLEMENTATION_LOG.md
Ильяс Султанов a54ec775dd perf(db): миграция 0079 — pg_trgm GIN-индексы поиска и горячие индексы
Шаг 0.11 плана production-готовности:
- триграммные GIN на tasks.title/description (ilike '%…%' без смены семантики)
- tasks(organization_id, updated_at DESC), tasks(form_id, updated_at)
- field_history(task_id), users(email)

Проверено на 100k задач: Seq Scan 77.5ms → Bitmap Index Scan 4.8ms (~16x)
2026-09-07 21:09:50 +03:00

90 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# IMPLEMENTATION_LOG — доведение iistwin до коммерческой готовности
## Контекст для любого ИИ-агента / разработчика
**Проект:** iistwin (TaskTitan) — CRM/таск-трекер. Стек: React 18 + TypeScript + Vite + Wouter + TanStack Query + shadcn/ui (клиент `client/src`), Express + Drizzle ORM + PostgreSQL с мультитенантностью через `organizationId` (сервер `server/`), JWT (access + refresh), SSE real-time. Миграции — детерминированный SQL-раннер в `migrations/` (`scripts/migrate-docker.js`, НЕ drizzle-kit). Деплой: push в `main` → GitHub Actions `deploy.yml` → Docker на VPS 82.202.130.22 (прод: https://iistwin.ru). Финансы — субапп `/di2` (`client-di2/` + `server/finance-di2/`).
**Инвариант (жёсткое требование владельца):** механика мгновенного глобального поиска — на главной (`client/src/components/HomeGlobalSearch.tsx`), в реестре задач (`TaskRegistry.tsx`) и в канбане (`TaskKanban.tsx`) — НЕ должна измениться ни в поведении, ни в скорости ощущений. API `GET /api/tasks?search=&limit=` и подстроковая семантика поиска (`ilike '%…%'` находит совпадения внутри слов, «омент» → «моментально») сохраняются. Ускорение поиска — только через pg_trgm GIN-индексы, НЕ через tsvector (он меняет семантику).
**Источники:** этот лог ведётся по плану «план улучшения до продакт.md» (аудит production-готовности) и исполнительному плану Kimi Code. Нумерация шагов ниже соответствует исходному плану (Фаза 0: 0.0–0.15, Фаза 1: 1.1–1.10, Фаза 2: 2.1–2.5). Дополнительный шаг A3 (подключение тестов) вынесен вперёд из 1.6.
**Принятые решения:**
- Биллинг (0.13): только абстракция `PaymentProvider` + `MockPaymentProvider`; реальный эквайринг — позже, отдельным этапом.
- Шаг 0.14: серверные кастомизации — в отдельный приватный инфра-репозиторий (основной репозиторий остаётся как есть).
**Как читать файл:** записи идут в порядке выполнения (не обязательно совпадает с нумерацией). Каждая запись — по шаблону: статус, зачем, что изменено, ключевые файлы, коммит, как проверялось, влияние на поиск/UX, подводные камни. Ничего не менять без записи в этот лог.
---
## [0.0] Обновить локальный репозиторий
- Статус: ✅ done
- Зачем: рабочая копия должна соответствовать актуальному `main` на GitHub.
- Что изменено: — (только проверка)
- Как проверялось: `git fetch origin` + `git status` (чисто, синхронизировано с origin/main, HEAD `a2b231d`), `npm run check` — зелёный (оба tsc: основной и client-di2).
- Влияние на поиск/UX: нет.
---
## [0.15] Сквозная документация изменений (этот файл)
- Статус: ✅ done (коммит `76a0d85` «chore(docs): add implementation log»)
- Зачем: по одному файлу любой ИИ-агент понимает, что сделано, зачем, какие файлы тронуты и как проверялось.
- Что изменено: создан `IMPLEMENTATION_LOG.md` в корне репозитория (шапка-контекст + шаблон записей).
- Влияние на поиск/UX: нет.
---
## [A3 / 1.6 частично] Подключение тестов + починка 5 падающих
- Статус: ✅ done
- Зачем: тесты существовали (3 файла, vitest), но не запускались ни локально (нет скрипта), ни в CI; 5 из 45 падали. Без зелёных тестов дальнейшие изменения безопасности/производительности — вслепую.
- Что изменено:
- `package.json` — скрипт `"test": "vitest run"`.
- `.github/workflows/pr-check.yml` — шаг «Tests» (`npm test`) после type check.
- `server/utils/userActivity.ts` — **фикс реального бага**: `trackUserActivity` мог кинуть синхронно внутри `authenticateToken`, исключение попадало в catch аутентификации → ложный `403 Недействительный токен` на первом запросе пользователя за минуту. Тело обёрнуто в try/catch, throttle сбрасывается для повторной попытки (соответствие контракту fire-and-forget).
- `tests/auto-transitions.test.ts` — 3 устаревших теста обновлены под намеренное текущее поведение (forward-only по position; синк assignee в task_assignees; системное сообщение заменено аудит-записью «Автопереход»), добавлен мок `delegation.service`.
- Как проверялось: `npx vitest run` — 45/45 зелёные; `npm run check` — чисто.
- Влияние на поиск/UX: нет.
- Подводные камни: баг в `userActivity.ts` проявлялся только на первом аутентифицированном запросе в минуту (throttle) — в проде мог давать sporadic 403.
---
## [0.7] Обработчик ошибок без утечки деталей
- Статус: ✅ done
- Зачем: клиент не должен видеть SQL и внутренности БД в 5xx-ответах (аудит, Фаза 0).
- Что изменено:
- `server/index.ts` — глобальный error handler: при status ≥ 500 отдаёт «Внутренняя ошибка сервера (код XXXXXXXX)», оригинальная ошибка — в серверный лог с тем же кодом инцидента. 4xx не тронуты.
- Route-уровень (12 файлов): зачищены все 5xx-ответы с `err.message`/`String(err)`/телом апстрима — `auth.users.routes.ts`, `form-offline.routes.ts`, `sync.routes.ts`, `polls.routes.ts`, `reactions.routes.ts`, `mcp-rag.routes.ts`, `llm-providers.routes.ts`, `external.routes.ts`, `task-crud-write.routes.ts`, `task-fields.routes.ts`, `finance-di2/routes.ts`, `documents/worker/server.ts`. Где не было — добавлен `console.error` с контекстом.
- Как проверялось: повторный grep `status(5xx)` + `err.message` по `server/` — 0 совпадений; `npm run check` чисто; `npx vitest run` 45/45.
- Влияние на поиск/UX: нет (только тексты 5xx-ответов).
- Подводные камни: остались ответы `200 + success:false` с `err?.message` (llm-providers, rag, finance-di2) и 4xx с `err.message` (documents, data-tables) — сознательно вне скоупа шага, кандидаты для фазы 2.
---
## [0.8] Убрать passwordHash из выдачи пользователей
- Статус: ✅ done
- Зачем: хэши паролей и токены не должны уходить клиенту (в т.ч. через offline-sync).
- Что изменено:
- `shared/schema.ts` — тип `SafeUser` (User без passwordHash/verificationToken/resetPasswordToken/resetPasswordExpires) + набор колонок `safeUserColumns` (единая точка правды).
- `server/storage/users.storage.ts` — `getUsersByOrganization` и `listUsers` → `SafeUser[]`.
- `server/storage/social.storage.ts` (`searchUsers`), `server/storage/task-meta.storage.ts` (`getUsersByRole`, `getUsersByOrgRoleId`), `server/documents/data-resolution.service.ts` (getUser для шаблонов) — тоже на `safeUserColumns`.
- Сигнатуры в `server/storage.ts`; типовые правки у caller'ов (логика не менялась). Методы аутентификации (где passwordHash нужен) не тронуты.
- Побочный эффект: `ctx.users.list()` в автоматизациях и MCP `list_users` тоже sanitized.
- Как проверялось: ни один из 43 caller'ов не использовал исключённые поля (tsc); sync (initial + delta) теперь отдаёт пользователей без хэшей; `npm run check` чисто; `npx vitest run` 45/45; grep `passwordHash` по server/ — только auth-флоу.
- Влияние на поиск/UX: нет.
- Подводные камни: при явном списке колонок drizzle возвращает плоские строки даже с join — мёртвый маппинг в listUsers убран, на это опираться нельзя в будущих правках.
---
## [0.11] Миграция индексов (0079_performance_indexes.sql)
- Статус: ✅ done
- Зачем: убрать Seq Scan на горячих путях — поиск задач, дельта-синк, история полей, логин по email.
- Что изменено: `migrations/0079_performance_indexes.sql` — `CREATE EXTENSION IF NOT EXISTS pg_trgm`; GIN (gin_trgm_ops) на `tasks.title` и `tasks.description`; `(organization_id, updated_at DESC)` и `(form_id, updated_at)` на tasks; `field_history(task_id)`; `users(email)`. Все с `IF NOT EXISTS`, без CONCURRENTLY (раннер в транзакции).
- Как проверялось: миграция применена на scratch-контейнере postgres:16-alpine с 100k синтетических задач — все 7 операторов чисто. EXPLAIN ANALYZE поиска `ilike '%омент%'` (совпадение внутри слова — инвариант): полный проход 77.5 мс (Seq Scan) → 4.8 мс (Bitmap Index Scan по trgm + BitmapAnd с org-индексом), ускорение ~16×; с LIMIT 50 — 10.8 мс → 9.7 мс. Отдельно проверено: `CREATE EXTENSION pg_trgm` выполняется не-суперпользователем-владельцем БД (trusted extension, PG 13+) — на проде миграция пройдёт под appuser.
- Влияние на поиск/UX: семантика НЕ меняется (тот же ilike, тот же API) — только скорость.
- Подводные камни: на проде построение GIN-индексов без CONCURRENTLY кратковременно блокирует запись в tasks — при текущих объёмах (десятки тысяч задач) это секунды, приемлемо.