From aa3bf3cbed3544557b5902c71ee1fbb48fd67e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 2 Jul 2026 20:30:44 +0300 Subject: [PATCH] =?UTF-8?q?feat(bot):=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D0=B0=20knowledge=5Fchunks=20=E2=80=94=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B7=D0=B0=20=D0=B7=D0=BD=D0=B0=D0=BD=D0=B8=D0=B9=20=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=20(FTS=20russian=20+=20GIN)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2_100001_create_knowledge_chunks_table.php | 42 ++++++++++ .../Feature/Bot/KnowledgeChunksTableTest.php | 30 +++++++ db/CHANGELOG_schema.md | 12 ++- db/schema.sql | 84 ++++++++++++++++++- 4 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 app/database/migrations/2026_07_02_100001_create_knowledge_chunks_table.php create mode 100644 app/tests/Feature/Bot/KnowledgeChunksTableTest.php diff --git a/app/database/migrations/2026_07_02_100001_create_knowledge_chunks_table.php b/app/database/migrations/2026_07_02_100001_create_knowledge_chunks_table.php new file mode 100644 index 00000000..74c51b60 --- /dev/null +++ b/app/database/migrations/2026_07_02_100001_create_knowledge_chunks_table.php @@ -0,0 +1,42 @@ +insert([ + 'source_path' => 'help/project.md', + 'title' => 'Что такое проект', + 'tour' => 'create-project', + 'topics' => 'заявка на лиды, создать проект, источник', + 'chunk_index' => 0, + 'content' => 'Проект — это заявка на поток лидов с выбранного источника.', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $found = DB::select( + "SELECT id, title FROM knowledge_chunks + WHERE search_tsv @@ websearch_to_tsquery('russian', ?)", + ['что такое проект'] + ); + + expect($found)->toHaveCount(1) + ->and($found[0]->title)->toBe('Что такое проект'); +}); diff --git a/db/CHANGELOG_schema.md b/db/CHANGELOG_schema.md index 914a1742..4a0ff08b 100644 --- a/db/CHANGELOG_schema.md +++ b/db/CHANGELOG_schema.md @@ -2,7 +2,17 @@ **Назначение:** консолидированный журнал изменений `schema.sql`. Содержит тридцать записей в обратном хронологическом порядке (v8.33 → v8.32 → v8.31 → v8.30 → v8.29 → v8.28 → v8.27 → v8.26 → v8.25 → v8.24 → v8.23 → v8.22 → v8.21 → v8.20 → v8.19 → v8.18 → v8.17 → v8.16 → v8.15 → v8.14 → v8.13 → v8.12 → v8.11 → v8.10 → v8.9 → v8.8 → v8.7 → v8.6 → v8.5 → v8.4 → v8.3 → v8.2), как принято в keep-a-changelog. -**Файл схемы:** `schema.sql` (текущая версия — v8.57, консолидированная — разворачивает БД с нуля). +**Файл схемы:** `schema.sql` (текущая версия — v8.58, консолидированная — разворачивает БД с нуля). + +## v8.58 (2026-07-02) — ИИ-бот техподдержки Jivo: knowledge_chunks + bot_dialogs + ++`knowledge_chunks` (база знаний ИИ-бота, глобальная, без RLS — публичные статьи инструкции resources/help/*.md; +generated column `search_tsv` russian + GIN-индекс `idx_knowledge_chunks_search`; заполняется командой +`help:rebuild-knowledge`; спека `docs/superpowers/specs/2026-07-02-jivo-ai-support-bot-design.md §3`). + ++`bot_dialogs` (журнал диалогов бота, глобальная, без RLS — ПДн клиентов не пишем; `direction in/out`, +`matched_chunks JSONB`, `latency_ms`, `escalated`; индекс `idx_bot_dialogs_chat (jivo_chat_id, created_at)`; +спека §5). Счётчики: таблиц 79→81 (regular 69→71) / индексов 124→126. Миграции 2026_07_02_100001/100002. ## v8.57 (2026-06-26) — RLS GUC hardening: NULLIF во всех политиках tenant_isolation (инцидент входа) diff --git a/db/schema.sql b/db/schema.sql index d56a33f1..810b2058 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1,6 +1,7 @@ -- ============================================================================= -- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра») --- Версия: v8.57 (26.06.2026 — RLS GUC hardening: ВСЕ 44 политики tenant_isolation приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint — устранён класс отказов на Managed PG/PgBouncer, когда GUC app.current_tenant_id пуст ('' → 22P02) либо не задан (→ 42704). Инцидент 26.06: вход в портал падал на резолве users (60 ошибок, все на users). 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно получили разрешающую ветку «NULLIF(...) IS NULL OR ...» — читаются/пишутся ДО tenant-контекста на auth-роутах без 'tenant' middleware; при ЗАДАННОМ tenant изоляция НЕ меняется (rls-reviewer APPROVE). Структурно БД НЕ меняется — переписаны только USING/WITH CHECK (счётчики таблиц/индексов/RLS=44/функций/триггеров без изменений). Миграция 2026_06_26_153000_rls_nullif_guc_hardening (идемпотентна). Применена на боевой кластер; lead_charges FORCE RLS сохранён.) +-- Версия: v8.58 (02.07.2026 — ИИ-бот техподдержки Jivo: +2 таблицы (knowledge_chunks — база знаний FTS russian GIN, глобальная без RLS; bot_dialogs — журнал диалогов, глобальная без RLS) + 2 индекса (idx_knowledge_chunks_search GIN, idx_bot_dialogs_chat). Спека docs/superpowers/specs/2026-07-02-jivo-ai-support-bot-design.md. Миграции 2026_07_02_100001/100002. Счётчики: таблиц 79→81 (regular 69→71) / индексов 124→126. RLS/функций/триггеров без изменений.) +-- Базовая версия: v8.57 (26.06.2026 — RLS GUC hardening: ВСЕ 44 политики tenant_isolation приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint — устранён класс отказов на Managed PG/PgBouncer, когда GUC app.current_tenant_id пуст ('' → 22P02) либо не задан (→ 42704). Инцидент 26.06: вход в портал падал на резолве users (60 ошибок, все на users). 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно получили разрешающую ветку «NULLIF(...) IS NULL OR ...» — читаются/пишутся ДО tenant-контекста на auth-роутах без 'tenant' middleware; при ЗАДАННОМ tenant изоляция НЕ меняется (rls-reviewer APPROVE). Структурно БД НЕ меняется — переписаны только USING/WITH CHECK (счётчики таблиц/индексов/RLS=44/функций/триггеров без изменений). Миграция 2026_06_26_153000_rls_nullif_guc_hardening (идемпотентна). Применена на боевой кластер; lead_charges FORCE RLS сохранён.) -- Базовая версия: v8.56 (26.06.2026 — Путь А, шов C: тело функции audit_block_mutation() пропускает пересчёт hash-цепочки по метке GUC app.audit_rebuild='on' (+ superuser ИЛИ член crm_migrator) ВМЕСТО session_replication_role (superuser-only, недоступен в Yandex Managed PG). Append-only сохранён: без метки UPDATE/DELETE аудита запрещён. Структурно БД НЕ меняется — только тело функции (счётчики таблиц/индексов/RLS/функций/триггеров без изменений, функций по-прежнему 5). Миграция 2026_06_26_140000_audit_block_mutation_guc_rebuild_flag. AuditRebuildChain команда переведена на SET LOCAL app.audit_rebuild в транзакции (Odyssey-safe).) -- Базовая версия: v8.55 (25.06.2026 — Эпик 5 отчёт заливки: +1 таблица supplier_sync_runs (сводка по вечерней заливке проектов поставщику — групп/синк/ручная/отложено/упало + status; SaaS-level без RLS/tenant_id как supplier_csv_reconcile_log, пишет crm_supplier_worker BYPASSRLS, читает SaaS-admin) + 1 явный индекс idx_supplier_sync_runs_created. Миграция 2026_06_25_130000. Структурно +1 regular-таблица + 1 индекс. NB: сводные счётчики несут известный дрейф рантайм-счётчика (ср. сверка 23.06) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.) -- Базовая версия: v8.54 (25.06.2026 — Эпик 4 online-defer: +1 таблица supplier_deferred_sync (системная очередь отложенных онлайн-правок в окне 18:00→00:00 МСК, project_id PK → projects ON DELETE CASCADE, без RLS/tenant_id — доступ только crm_supplier_worker BYPASSRLS, покрыт blanket-грантом ON ALL TABLES в db/02_grants.sql как supplier_manual_sync_queue). Миграция 2026_06_25_120000. Структурно +1 regular-таблица; явных CREATE INDEX +0 (PK неявный). NB: сводные счётчики таблиц/индексов несут известный дрейф рантайм-счётчика (ср. сверка 23.06 RLS 42→44) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.) @@ -29,7 +30,7 @@ -- Базовая версия: v8.29 (22.05.2026 — webhook_log: supplier audit columns) -- Базовая версия: v8.28 (22.05.2026 — tenant_operations_log: журнал тенант-уровневых операций вне сделок (проекты, API-ключи, webhook URL), append-only hash-chain, P2 operational journaling closure) -- Базовая версия: v8.27 (21.05.2026 — drop projects.archived_at: feature архива заменена настоящим удалением с защитой по сделкам (ProjectService::delete())) --- Метрики: 79 базовых таблиц (69 regular + 10 partitioned parents: deals + supplier_lead_costs + 6 audit + lead_region_resolution_log + project_routing_snapshots) + 16 партиций / 124 индекса (+1 явный: idx_supplier_sync_runs_created) / 44 RLS-политик / 5 функций / 15 триггеров +-- Метрики: 81 базовая таблица (71 regular + 10 partitioned parents: deals + supplier_lead_costs + 6 audit + lead_region_resolution_log + project_routing_snapshots) + 16 партиций / 126 индексов (+1 явный: idx_supplier_sync_runs_created; +idx_knowledge_chunks_search GIN; +idx_bot_dialogs_chat) / 44 RLS-политик / 5 функций / 15 триггеров -- NB (23.06.2026 сверка-чистка перед запуском): RLS-политик исправлено 42→44 — реальное число активных CREATE POLICY в теле == 44 == на боевом liderra.ru (pg_policies). Расхождение было исторический недоучёт в бегущем счётчике шапки (tenant_requisites v8.43 и др. добавлены в тело без правки сводной метрики). Структурно БД не менялась — только корректность числа. -- Базовая версия: v8.25 (19.05.2026 — supplier_manual_sync_queue: SaaS-level Tier 3 очередь резерва канала миграции проектов) -- Базовая версия: v8.24 (18.05.2026 — supplier_leads.vid → nullable для CSV-recovered лидов (Путь 2)) @@ -3639,6 +3640,85 @@ BEGIN END IF; END $$; +-- ============================================================================= +-- knowledge_chunks — база знаний ИИ-бота (v8.58, спека 2026-07-02-jivo-ai-support-bot-design §3) +-- ============================================================================= +-- Глобальная таблица (НЕ tenant-scoped): только публичные статьи клиентской инструкции, +-- данных клиентов здесь нет по определению — RLS не требуется. +-- search_tsv — generated column (russian): title+topics+content → tsvector + GIN-индекс. +-- Заполняется командой help:rebuild-knowledge (ночной schedule 04:30). +-- Миграция 2026_07_02_100001_create_knowledge_chunks_table. +-- ============================================================================= +CREATE TABLE knowledge_chunks ( + id BIGSERIAL PRIMARY KEY, + source_path VARCHAR(255) NOT NULL, + title VARCHAR(255) NOT NULL, + tour VARCHAR(100), + topics TEXT NOT NULL DEFAULT '', + chunk_index INTEGER NOT NULL, + content TEXT NOT NULL, + search_tsv tsvector GENERATED ALWAYS AS ( + to_tsvector('russian', coalesce(title, '') || ' ' || coalesce(topics, '') || ' ' || coalesce(content, '')) + ) STORED, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_knowledge_chunks_source_chunk UNIQUE (source_path, chunk_index) +); + +CREATE INDEX idx_knowledge_chunks_search ON knowledge_chunks USING GIN (search_tsv); + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_user') THEN + GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_app_user; + GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_app_user; + END IF; + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_admin') THEN + GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_app_admin; + GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_app_admin; + END IF; + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_migrator') THEN + GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_migrator; + GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_migrator; + END IF; +END $$; + +-- ============================================================================= +-- bot_dialogs — журнал диалогов ИИ-бота (v8.58, спека §5) +-- ============================================================================= +-- Глобальная (НЕ tenant-scoped) в v1: диалоги Jivo анонимны до этапа личных ответов; +-- ПДн клиентов не пишем. direction: in = сообщение клиента, out = ответ бота. +-- Миграция 2026_07_02_100002_create_bot_dialogs_table. +-- ============================================================================= +CREATE TABLE bot_dialogs ( + id BIGSERIAL PRIMARY KEY, + jivo_chat_id VARCHAR(64) NOT NULL, + direction VARCHAR(3) NOT NULL CHECK (direction IN ('in', 'out')), + message TEXT NOT NULL, + matched_chunks JSONB, + latency_ms INTEGER, + escalated BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX idx_bot_dialogs_chat ON bot_dialogs (jivo_chat_id, created_at); + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_user') THEN + GRANT SELECT, INSERT ON bot_dialogs TO crm_app_user; + GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_app_user; + END IF; + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_admin') THEN + GRANT SELECT, INSERT ON bot_dialogs TO crm_app_admin; + GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_app_admin; + END IF; + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_migrator') THEN + GRANT SELECT, INSERT ON bot_dialogs TO crm_migrator; + GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_migrator; + END IF; +END $$; + -- ============================================================================= -- КОНЕЦ schema.sql -- =============================================================================