Шаг 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)
12 KiB
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, HEADa2b231d),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 run45/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()в автоматизациях и MCPlist_usersтоже sanitized.
- Как проверялось: ни один из 43 caller'ов не использовал исключённые поля (tsc); sync (initial + delta) теперь отдаёт пользователей без хэшей;
npm run checkчисто;npx vitest run45/45; greppasswordHashпо 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 — при текущих объёмах (десятки тысяч задач) это секунды, приемлемо.